//=======================================================================================
//
//  Purpose: Povide a method of controlling a 8 x 8 x 8 LED cube
//
//  Copyright (C) 2011 Mark Stevens
//
//  This software is destributed under the MS-PL licence agreement a copy of which can
//  be found on the codeplex page http://netduinoledcontrol.codeplex.com and in the
//  Licence.txt file distributed with this project.
//
//=======================================================================================
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.NetduinoMini;
using System.Threading;

namespace Coding4Fun.NetduinoLEDControl
{
    /// <summary>
    /// Methods and data required to control a LED cube using shift registers and
    /// 3 to 8 line multiplexers.
    /// </summary>
    class LEDCube
    {
        #region Private variables

        /// <summary>
        /// SPI bus to use to send data to the shift registers.
        /// </summary>
        private SPI _spi = null;

        /// <summary>
        /// CSPI bus configuration
        /// </summary>
        private SPI.Configuration _config;

        /// <summary>
        /// Buffer holding the display data.
        /// </summary>
        private byte[] _buffer;

        /// <summary>
        /// Bit 0 of the address for the 74HC238
        /// </summary>
        private OutputPort _bit0 = new OutputPort(Pins.GPIO_PIN_10, false);

        /// <summary>
        /// Bit 1 of the address for the 74HC238
        /// </summary>
        private OutputPort _bit1 = new OutputPort(Pins.GPIO_PIN_9, false);

        /// <summary>
        /// Bit 2 of the address for the 74HC238
        /// </summary>
        private OutputPort _bit2 = new OutputPort(Pins.GPIO_PIN_8, false);

        /// <summary>
        /// Output enable pin for the 74HC238
        /// </summary>
        private OutputPort _enable = new OutputPort(Pins.GPIO_PIN_6, true);

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor for the LEDCube class.
        /// </summary>
        public LEDCube()
        {
            _config = new SPI.Configuration(SPI_mod: SPI.SPI_module.SPI1,
                                           ChipSelect_Port: Pins.GPIO_PIN_20,
                                           ChipSelect_ActiveState: false,
                                           ChipSelect_SetupTime: 0,
                                           ChipSelect_HoldTime: 0,
                                           Clock_IdleState: true,
                                           Clock_Edge: true,
                                           Clock_RateKHz: 2000);

            _spi = new SPI(_config);
            _buffer = new byte[64];
            for (int index = 0; index < _buffer.Length; index++)
            {
                _buffer[index] = 0;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Main loop which continuously updates the display from the buffer.
        /// </summary>
        public void DisplayBuffer()
        {
            while (true)
            {
                lock (_buffer)
                {
                    byte[] displayData = new byte[8];

                    for (int row = 0; row < 8; row++)
                    {
                        int offset = row * 8;
                        for (int index = 0; index < 8; index++)
                        {
                            displayData[index] = _buffer[offset + index];
                        }
                        _enable.Write(false);            // Turn all of the layers off
                        _bit0.Write((row & 1) != 0);     // Set the new layer.
                        _bit1.Write((row & 2) != 0);
                        _bit2.Write((row & 4) != 0);
                        _spi.Write(displayData);         // Output the data to the 74HC595's
                        _enable.Write(true);             // Turn the newly selected layer on.
                    }
                }
            }
        }

        /// <summary>
        /// Change the byte in the display buffer.
        /// </summary>
        /// <param name="newValues">Bytes to put into the cube display buffer.</param>
        public void UpdateBuffer(byte[] newValues)
        {
            lock (_buffer)
            {
                for (int index = 0; index < _buffer.Length; index++)
                {
                    _buffer[index] = newValues[index];
                }
            }
        }

        /// <summary>
        /// Clear the buffer and update the cube.
        /// </summary>
        public void ClearCube()
        {
            lock (_buffer)
            {
                for (int index = 0; index < _buffer.Length; index++)
                {
                    _buffer[index] = 0;
                }
            }
        }

        #endregion
    }
}
